home *** CD-ROM | disk | FTP | other *** search
- /* Sprite Blitz Solution by Xan Gregg, July 1995
- Since "correctness" is considered before speed in judging
- solutions, this solution makes correctness the top priority
- at the cost of speed.
-
- I use two offscreen GWorlds. One has the background, and
- another has the image to be displayed on the screen next.
- The "on deck" image is updated sprite by sprite, then it is
- copied to the screen for minimum flicker.
-
- The GWorlds are a little bigger than the screen so I don't
- have to worry about sprites that overlap the edges until
- copying to the screen.
-
- Memory usage:
- 2 GWorlds, each 64 pixels wider and taller than window.
- 1K of pixel data for each sprite.
- 128 bytes of mask data for each sprite.
- 16 bytes of info for each sprite.
- I set the number of sprites to 400. The problem states a
- maximum of 200 present at a time, but because a deleted
- sprite stays around until the next UpdateScreen() call,
- I allow for 400 in case you delete all 200 then add 200
- more before calling UpdateScreen(). Paranoid, but if
- you've got the memory...
-
- Assumptions not stated in the problem:
- Enough memory available for above usage.
- Window width is a multiple of 4 (confirmed by BB).
- Window does not move during play.
-
- */
-
- #include <QDOffscreen.h>
- /*#include "July.h" /* prototypes */
-
- typedef struct
- {
- short nextSlot;
- short status;
- short width;
- short height;
- Point position;
- Point lastPosition;
- } SpriteInfo, *SpriteInfoPtr;
-
- typedef struct
- {
- char pixData[1024];
- } SpritePixData, *SpritePixDataPtr;
-
- typedef struct
- {
- char maskData[128];
- } SpriteMaskData, *SpriteMaskDataPtr;
-
-
- #define kMaxSprites 400L
- #define kMaxSpriteWidth 32L
- #define kMaxSpriteHeight 32L
-
- static CWindowPtr gScreenWindowP;
- static GWorldPtr gBackgroundGW;
- static PixMapHandle gBackgroundPixMapH;
- static GWorldPtr gOnDeckGW;
- static PixMapHandle gOnDeckPixMapH;
- static short gLastSpriteSlot;
- static short gFirstSpriteSlot;
- static short gSpriteCount;
- static short gWindowWidth;
- static short gWindowHeight;
- static SpriteInfoPtr gSpriteInfo;
- static SpritePixDataPtr gSpritePixData;
- static SpriteMaskDataPtr gSpriteMaskData;
- static long gOnDeckRowBytes;
- static Ptr gOnDeckBaseAddr;
- static long gBkgRowBytes;
- static Ptr gBkgBaseAddr;
- static long gScreenRowBytes;
- static Ptr gScreenBaseAddr;
- static Ptr *gBkgRowAddr;
- static Ptr *gOnDeckRowAddr;
- static Ptr *gScreenRowAddr;
- static short gDeletionCount;
-
-
-
-
- void StartGame(CWindowPtr windowP)
- {
- Rect r;
- PixMapPtr bkgPixMapP;
- PixMapPtr onDeckPixMapP;
- PixMapPtr screenPixMapP;
-
- gLastSpriteSlot = -1;
- gFirstSpriteSlot = -1;
- gSpriteCount = 0;
- gDeletionCount = 0;
- gScreenWindowP = windowP;
- r = windowP->portRect;
- OffsetRect(&r, -r.left, -r.top);
- gWindowWidth = r.right;
- gWindowHeight = r.bottom;
-
- InsetRect(&r, -kMaxSpriteWidth, -kMaxSpriteHeight);
- NewGWorld(&gBackgroundGW, 0, &r, 0, 0, 0);
- gBackgroundPixMapH = GetGWorldPixMap(gBackgroundGW);
- LockPixels(gBackgroundPixMapH); /* always locked */
- NewGWorld(&gOnDeckGW, 0, &r, 0, 0, 0);
- gOnDeckPixMapH = GetGWorldPixMap(gOnDeckGW);
- LockPixels(gOnDeckPixMapH); /* always locked */
-
- gSpriteInfo = (SpriteInfoPtr) NewPtrClear
- (sizeof(SpriteInfo) * kMaxSprites);
- gSpritePixData = (SpritePixDataPtr) NewPtrClear
- (sizeof(SpritePixData) * kMaxSprites);
- gSpriteMaskData = (SpriteMaskDataPtr) NewPtrClear
- (sizeof(SpriteMaskData) * kMaxSprites);
- gBkgRowAddr = (Ptr *) NewPtr(sizeof(Ptr) *
- (gWindowHeight + kMaxSpriteHeight * 2));
- gOnDeckRowAddr = (Ptr *) NewPtr(sizeof(Ptr) *
- (gWindowHeight + kMaxSpriteHeight * 2));
- gScreenRowAddr = (Ptr *) NewPtr(sizeof(Ptr)
- * (long) gWindowHeight);
- if (gSpriteInfo == 0 || gSpritePixData == 0
- || gSpriteMaskData == 0 || gScreenRowAddr == 0
- || gBkgRowAddr == 0 || gOnDeckRowAddr == 0
- || gBackgroundGW == 0 || gOnDeckGW == 0)
- DebugStr("\p out of memory!");
- InsetRect(&r, kMaxSpriteWidth, kMaxSpriteHeight);
- OffsetRect(&r, kMaxSpriteWidth, kMaxSpriteHeight);
- CopyBits(&((WindowPtr)windowP)->portBits,
- &((WindowPtr)gBackgroundGW)->portBits,
- &windowP->portRect, &r, srcCopy, NULL);
- CopyBits(&((WindowPtr)windowP)->portBits,
- &((WindowPtr)gOnDeckGW)->portBits,
- &windowP->portRect, &r, srcCopy, NULL);
-
- bkgPixMapP = *gBackgroundPixMapH;
- onDeckPixMapP = *gOnDeckPixMapH;
- gOnDeckRowBytes = onDeckPixMapP->rowBytes & 0x7fff;
- gOnDeckBaseAddr = onDeckPixMapP->baseAddr
- + gOnDeckRowBytes * kMaxSpriteHeight
- + kMaxSpriteWidth;
- gBkgRowBytes = bkgPixMapP->rowBytes & 0x7fff;
- gBkgBaseAddr = bkgPixMapP->baseAddr
- + gBkgRowBytes * kMaxSpriteHeight
- + kMaxSpriteWidth;
- screenPixMapP = *gScreenWindowP->portPixMap;
- gScreenRowBytes = screenPixMapP->rowBytes & 0x7fff;
- gScreenBaseAddr = screenPixMapP->baseAddr
- - screenPixMapP->bounds.left
- - screenPixMapP->bounds.top
- * gScreenRowBytes;
-
- { /* initialize rowAddr's */
- long row;
-
- gOnDeckRowAddr += kMaxSpriteHeight;
- gBkgRowAddr += kMaxSpriteHeight;
- for (row = -kMaxSpriteHeight;
- row < gWindowHeight + kMaxSpriteHeight; row++)
- {
- gBkgRowAddr[row] = gBkgBaseAddr
- + row * gBkgRowBytes;
- gOnDeckRowAddr[row] = gOnDeckBaseAddr
- + row * gOnDeckRowBytes;
- }
- for (row = 0; row < gWindowHeight; row++)
- gScreenRowAddr[row] = gScreenBaseAddr
- + row * gScreenRowBytes;
- }
- }
-
-
- /* make a copy of CIcon's pixel and mask data */
- short AddSprite(CIconPtr cIconP, Point startPt)
- {
- short slot;
- short i;
- short pixWidth;
- short maskWidth;
- short pixBytes;
- short maskBytes;
- short bitBytes;
- short height;
- Ptr pixSrcAddr, pixDstAddr;
- long *maskSrcAddr, *maskDstAddr;
-
- slot = gLastSpriteSlot + 1;
- if (slot == kMaxSprites)
- slot = 0;
- while (gSpriteInfo[slot].status != 0)
- {
- slot++;
- if (slot == kMaxSprites)
- slot = 0;
- }
- gSpriteInfo[slot].status = 1; /* occupied */
- height = cIconP->iconPMap.bounds.bottom
- - cIconP->iconPMap.bounds.top;
- pixWidth = cIconP->iconPMap.bounds.right
- - cIconP->iconPMap.bounds.left;
- maskWidth = (pixWidth + 7) >> 3;
- gSpriteInfo[slot].width = pixWidth;
- gSpriteInfo[slot].height = height;
- pixBytes = cIconP->iconPMap.rowBytes & 0x7fff;
- maskBytes = cIconP->iconMask.rowBytes;
- bitBytes = cIconP->iconBMap.rowBytes;
- pixSrcAddr = ((Ptr) &cIconP->iconMaskData)
- + bitBytes * height
- + maskBytes * height
- + 256 * 8 + 8; /* 8-bit color table */
-
- pixDstAddr = (char *) &gSpritePixData[slot];
- maskSrcAddr = (long *) &cIconP->iconMaskData;
- maskDstAddr = (long *) &gSpriteMaskData[slot];
- pixWidth = pixWidth >> 2;
- for (i = 0; i < height; i++)
- {
- {
- register long *q = (long *) pixDstAddr;
- register long *p = (long *) pixSrcAddr;
- register short j = pixWidth;
- while (j > 0)
- {
- *q++ = *p++;
- j--;
- }
- }
- *maskDstAddr++ = *maskSrcAddr;
- pixDstAddr += 32;
- pixSrcAddr += pixBytes;
- maskSrcAddr = (long *) (((Ptr) maskSrcAddr)
- + maskBytes);
- }
-
- if (gLastSpriteSlot >= 0)
- {
- gSpriteInfo[gLastSpriteSlot].nextSlot = slot;
- }
- else
- {
- gFirstSpriteSlot = slot;
- }
- gLastSpriteSlot = slot;
- gSpriteInfo[slot].nextSlot = -1;
- gSpriteInfo[slot].position = startPt;
- gSpriteInfo[slot].lastPosition = startPt;
- gSpriteCount ++;
- return slot;
- }
-
-
- /* replace sprite with chunk from the bkg gworld */
- static void EraseSprite(SpriteInfoPtr spriteInfoP)
- {
- short numRows;
- short numCols;
- register long *p;
- register long *q;
- short h, v;
- register long srcRowBytes;
- register long dstRowBytes;
-
- numRows = spriteInfoP->height;
- numCols = spriteInfoP->width;
- h = spriteInfoP->lastPosition.h;
- v = spriteInfoP->lastPosition.v;
- if (h + numCols <= 0 || h >= gWindowWidth
- || v + numRows <= 0 || v >= gWindowHeight)
- return; /* totally offscreen, so skip it */
-
- p = (long *) (gBkgRowAddr[v] + h);
- q = (long *) (gOnDeckRowAddr[v] + h);
- srcRowBytes = gBkgRowBytes - numCols;
- dstRowBytes = gOnDeckRowBytes - numCols;
- if (numCols >= 16)
- if (numCols == 32)
- {
- while (numRows != 0)
- {
- numRows--;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- p = (long *) (((Ptr) p) + srcRowBytes);
- q = (long *) (((Ptr) q) + dstRowBytes);
- }
- }
- else
- {
- while (numRows != 0)
- {
- numRows--;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- p = (long *) (((Ptr) p) + srcRowBytes);
- q = (long *) (((Ptr) q) + dstRowBytes);
- }
- }
- else
- {
- if (numCols < 8)
- while (numRows != 0)
- {
- numRows--;
- *q = *p;
- p = (long *) (((Ptr) p) + gBkgRowBytes);
- q = (long *) (((Ptr) q) + gOnDeckRowBytes);
- }
- else /* erase 4 pixels, even if its smaller */
- while (numRows != 0)
- {
- numRows--;
- *q++ = *p++;
- *q++ = *p++;
- p = (long *) (((Ptr) p) + srcRowBytes);
- q = (long *) (((Ptr) q) + dstRowBytes);
- }
- }
- }
-
-
- /* Don't actually do the delete, just mark for deletion.
- -- because we still need to erase it in UpdateScreen()
- */
- void DeleteSprite(short spriteID)
- {
- gSpriteInfo[spriteID].status = -1; /* to be deleted */
- gDeletionCount++;
- }
-
-
- /* only called when there is at least one deletion */
- static void RemoveDeletedSprites(void)
- {
- short prevSlot = -1;
- short slot = gFirstSpriteSlot;
- short count = gDeletionCount;
-
- while (1)
- {
- if (gSpriteInfo[slot].status < 0)
- { /* needs to be removed */
- if (prevSlot >= 0)
- gSpriteInfo[prevSlot].nextSlot
- = gSpriteInfo[slot].nextSlot;
- else
- gFirstSpriteSlot
- = gSpriteInfo[slot].nextSlot;
- if (slot == gLastSpriteSlot)
- gLastSpriteSlot = prevSlot;
- gSpriteInfo[slot].status = 0; /* available */
- gSpriteCount--;
- count--;
- if (count == 0)
- break;
- }
- else
- {
- prevSlot = slot;
- }
- slot = gSpriteInfo[slot].nextSlot;
- }
- gDeletionCount = 0;
- }
-
-
-
- void MoveSprite(short spriteID, Point deltaPt)
- {
- gSpriteInfo[spriteID].position.h += deltaPt.h;
- gSpriteInfo[spriteID].position.v += deltaPt.v;
- }
-
-
- static void EraseOldSprites(void)
- {
- short slot;
- SpriteInfoPtr spriteInfoP;
-
- slot = gFirstSpriteSlot;
- while (slot >= 0)
- {
- spriteInfoP = &gSpriteInfo[slot];
- EraseSprite(spriteInfoP);
- slot = spriteInfoP->nextSlot;
- }
-
- }
-
- /* copy 4 pixels based on bits of the mask */
- #define COPY4(q,p,m) \
- switch ((m) & 0x0f) \
- { \
- case 0x0: break; \
- case 0x1: *(q+3) = *(p+3); break; \
- case 0x2: *(q+2) = *(p+2); break; \
- case 0x3: *(short*)(q+2) = *(short*)(p+2); break; \
- case 0x4: *(q+1) = *(p+1); break; \
- case 0x5: *(q+1) = *(p+1); *(q+3) = *(p+3); break; \
- case 0x6: *(short*)(q+1) = *(short*)(p+1); break; \
- case 0x7: *(q+1) = *(p+1); \
- *(short*)(q+2) = *(short*)(p+2); break; \
- case 0x8: *(q) = *(p); break; \
- case 0x9: *(q) = *(p); *(q+3) = *(p+3); break; \
- case 0xA: *(q) = *(p); *(q+2) = *(p+2); break; \
- case 0xB: *(q) = *(p); \
- *(short*)(q+2) = *(short*)(p+2); break; \
- case 0xC: *(short*)(q) = *(short*)(p); break; \
- case 0xD: *(short*)(q) = *(short*)(p); \
- *(q+3) = *(p+3); break; \
- case 0xE: *(short*)(q) = *(short*)(p); \
- *(q+2) = *(p+2); break; \
- case 0xF: *(long*)(q) = *(long*)(p); break; \
- }
-
- #define COPY8(q,p,mask) \
- COPY4(q, p, mask >> 4) \
- COPY4(q+4,p+4, mask)
-
-
-
- static void DrawSprite(short slot)
- {
- SpriteInfoPtr spriteInfoP;
- short numRows;
- short numCols;
- register Ptr p;
- register Ptr q;
- Ptr maskP;
- short srcRowBytes;
- short h, v;
- short mask;
- short maskMask;
- short maskRowBytes;
- short numMaskBytes;
- short i;
- long dstRowBytes;
-
- spriteInfoP = &gSpriteInfo[slot];
- h = spriteInfoP->position.h;
- v = spriteInfoP->position.v;
- numRows = spriteInfoP->height;
- numCols = spriteInfoP->width;
- p = (char *) &gSpritePixData[slot];
- q = gOnDeckRowAddr[v] + h;
- maskP = (char *) &gSpriteMaskData[slot];
-
- if (numCols >= 8)
- {
- numMaskBytes = numCols >> 3;
- maskRowBytes = 4 - numMaskBytes;
- srcRowBytes = 40 - numCols;
- dstRowBytes = gOnDeckRowBytes - numCols + 8;
- while (1)
- {
- i = numMaskBytes;
- while (1)
- {
- mask = *maskP++;
- COPY8(q, p, mask)
- if (--i == 0)
- break;
- p += 8;
- q += 8;
- }
- if (--numRows == 0)
- break;
- maskP += maskRowBytes;
- p += srcRowBytes;
- q += dstRowBytes;
- }
- }
- else
- {
- maskMask = 0xf00 >> numCols;
- while (1)
- {
- mask = (*maskP) & maskMask;
- COPY8(q, p, mask)
- if (--numRows == 0)
- break;
- maskP += 4;
- p += 32;
- q += gOnDeckRowBytes;
- }
- }
- }
-
-
- static void DrawNewSprites(void)
- {
- register short slot;
- SpriteInfoPtr spriteInfoP;
-
- slot = gFirstSpriteSlot;
- while (slot >= 0)
- {
- register short numRows;
- register short numCols;
- register short h;
- register short v;
-
- spriteInfoP = &gSpriteInfo[slot];
- if (spriteInfoP->status < 0)
- goto nextSlot; /* deleted, so skip it */
- numRows = spriteInfoP->height;
- numCols = spriteInfoP->width;
- h = spriteInfoP->position.h;
- v = spriteInfoP->position.v;
- if (h + numCols <= 0 || h >= gWindowWidth
- || v + numRows <= 0 || v >= gWindowHeight)
- goto nextSlot; /* totally offscreen */
-
- DrawSprite(slot);
- nextSlot:
- slot = spriteInfoP->nextSlot;
- }
- }
-
-
- /* count is a multiple of 4 in the range [4..44] */
- static void FastCopyChunk(long *q, long *p,
- short count, short rows)
- {
- register short srcRowBytes;
- register short dstRowBytes;
- register short rowsLeft = rows;
- register short copy8 = count & 8;
- register short copy4 = count & 4;
-
- srcRowBytes = gOnDeckRowBytes - count;
- dstRowBytes = gScreenRowBytes - count;
- if (count & 32)
- {
- while (rowsLeft > 0)
- {
- rowsLeft--;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- if (copy8)
- {
- *q++ = *p++;
- *q++ = *p++;
- }
- if (copy4)
- *q++ = *p++;
- p = (long *) (((Ptr) p) + srcRowBytes);
- q = (long *) (((Ptr) q) + dstRowBytes);
- }
- }
- else if (count & 16)
- {
- while (rowsLeft > 0)
- {
- rowsLeft--;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- *q++ = *p++;
- if (copy8)
- {
- *q++ = *p++;
- *q++ = *p++;
- }
- if (copy4)
- *q++ = *p++;
- p = (long *) (((Ptr) p) + srcRowBytes);
- q = (long *) (((Ptr) q) + dstRowBytes);
- }
- }
- else
- {
- while (rowsLeft > 0)
- {
- rowsLeft--;
- if (copy8)
- {
- *q++ = *p++;
- *q++ = *p++;
- }
- if (copy4)
- *q++ = *p++;
- p = (long *) (((Ptr) p) + srcRowBytes);
- q = (long *) (((Ptr) q) + dstRowBytes);
- }
- }
- }
-
-
- /* Here we do have to watch out for sprites that overlap
- the edges of the window. We copy a rectangluar region
- that includes the sprites previous and current positions.
- We know they will be close sionce sprites move at most
- 8 pixels per turn.
- */
- static void DrawNewSpritesToScreen(void)
- {
- short slot;
- SpriteInfoPtr spriteInfoP;
- short numRows;
- short numCols;
- register long *p;
- register long *q;
- short hStart, hEnd;
- short vStart, vEnd;
-
- slot = gFirstSpriteSlot;
- while (slot >= 0)
- {
- spriteInfoP = &gSpriteInfo[slot];
- numRows = spriteInfoP->height;
- numCols = spriteInfoP->width;
- if (spriteInfoP->position.h
- < spriteInfoP->lastPosition.h)
- {
- hStart = spriteInfoP->position.h;
- hEnd = spriteInfoP->lastPosition.h + numCols;
- }
- else
- {
- hStart = spriteInfoP->lastPosition.h;
- hEnd = spriteInfoP->position.h + numCols;
- }
- if (hStart < 0)
- hStart = 0;
- else if (hEnd > gWindowWidth)
- hEnd = gWindowWidth;
- if (spriteInfoP->position.v
- < spriteInfoP->lastPosition.v)
- {
- vStart = spriteInfoP->position.v;
- vEnd = spriteInfoP->lastPosition.v + numRows;
- }
- else
- {
- vStart = spriteInfoP->lastPosition.v;
- vEnd = spriteInfoP->position.v + numRows;
- }
- if (vStart < 0)
- vStart = 0;
- else if (vEnd > gWindowHeight)
- vEnd = gWindowHeight;
- hStart = hStart & -4; /* make it a mult of 4 */
- hEnd = (hEnd + 3) & -4; /* make it a mult of 4 */
-
- p = (long *) (gOnDeckRowAddr[vStart] + hStart);
- q = (long *) (gScreenRowAddr[vStart] + hStart);
-
- vEnd -= vStart; /* now it's a count */
- hEnd -= hStart; /* now it's a count */
- if (hEnd >= 0)
- FastCopyChunk(q, p, hEnd, vEnd);
-
- spriteInfoP->lastPosition = spriteInfoP->position;
- slot = spriteInfoP->nextSlot;
- }
- }
-
-
- void UpdateScreen(void)
- {
- EraseOldSprites();
- DrawNewSprites();
- DrawNewSpritesToScreen();
- if (gDeletionCount != 0)
- RemoveDeletedSprites();
- }
-
-
-
-
-
-
-
-